﻿#include "NodeSnatch.h"

static const int s_dragThreshold = 64;

NodeSnatch::~NodeSnatch()
{
	if (_parent)
	{
		_parent->removeChildSnatch(this);
		_parent = nullptr;
	}
}

bool NodeSnatch::init(Node* node)
{
	_nodeTarget = node;
	setState(eState::Normal);
	return true;
}


void NodeSnatch::setEnabled(bool isEnabled)
{
	if (_isEnabled == isEnabled)
	{
		return;
	}
	_isEnabled = isEnabled;
	if (!isEnabled)
	{
		setState(eState::None);
	}
}


void NodeSnatch::addChildSnatch(NodeSnatch *nodeSnatch, bool isAddchildForNodeTarget /* = true */)
{
	nodeSnatch->_parent = this;
	_children.push_back(nodeSnatch);
	if (isAddchildForNodeTarget)
	{
		_nodeTarget->addChild(nodeSnatch->_nodeTarget);
	}
}


void NodeSnatch::removeChildSnatch(NodeSnatch *nodeSnatch)
{
	forr(_children, i)
	{
		if (_children[i] == nodeSnatch)
		{
			_children.erase(_children.begin() + i);
			return;
		}
	}
}


bool NodeSnatch::isBox(const Vec2& worldSpace)
{
	const auto& size = _nodeTarget->getContentSize();
	return Rect(0, 0, size.width, size.height).containsPoint(_nodeTarget->convertToNodeSpace(worldSpace));
}




ListenerSnatch::~ListenerSnatch()
{
	_children.clear();
}



bool ListenerSnatch::init(Node* node)
{
	if (!EventListenerTouchOneByOne::init())
	{
		return false;
	}
	_nodeTarget = node;
	_shedulerKey = toString("%p", this);

#ifdef ccc_mouse
	EventListenerMouse* mouse = EventListenerMouse::create();

	mouse->onMouseMove = [&](EventMouse* e)
	{
		if (_isDragThreshold)
		{
			return;
		}
		const auto& top = getChildSnatch(Vec2(e->getCursorX(), e->getCursorY()));
		compareTops(top, _curr, false);
		_curr = top;
	};

	mouse->onMouseUp = [&](EventMouse* e)
	{
		if (e->getMouseButton() != EventMouse::MouseButton::BUTTON_RIGHT)
		{
			return;
		}
		if (!_curr || !_curr->isEnabled())
		{
			return;
		}
		_curr->doRup(Vec2(e->getCursorX(), e->getCursorY()));
	};

	mouse->onMouseScroll = [&](EventMouse* e)
	{
		Vec2 v(e->getCursorX(), e->getCursorY());
		const auto& top = getChildSnatch(v);
		compareTops(top, _curr, false);
		_curr = top;
		if (!_curr || !_curr->isEnabled() || _curr->_dragType == NodeSnatch::eDrag::None)
		{
			return;
		}
		Node* nodeDrag = _curr->getDragableNode();
		switch (_curr->_dragType)
		{
		case NodeSnatch::eDrag::H:
			nodeDrag->setPositionX(nodeDrag->getPositionX() + _curr->_scrollSpeed * e->getScrollY());
			_curr->doDraging(v);
			break;
		default:
			nodeDrag->setPositionY(nodeDrag->getPositionY() + _curr->_scrollSpeed * e->getScrollY());
			_curr->doDraging(v);
			break;
		}
	};

	_nodeTarget->getEventDispatcher()->addEventListenerWithSceneGraphPriority(mouse, _nodeTarget);
#endif 

	EventListenerTouchOneByOne *oneByone = this; // EventListenerTouchOneByOne::create();
	oneByone->setSwallowTouches(true);
	oneByone->onTouchBegan = [&](Touch*t, Event* e)
	{
		_delta = 0;
		_isDragThreshold = false;
		const auto& v = t->getLocation();
		const auto& top = getChildSnatch(v);
		compareTops(top, _curr, true);
		_curr = top;
		if (!_curr || !_curr->isEnabled())
		{
			return false;
		}
		if (_curr->_isSortable)
		{
			_curr->_nodeTarget->setLocalZOrder(++_z);
			sortChildren();
		}
		if (!_isSheduling)
		{
			EventListenerTouchOneByOne::getAssociatedNode()->schedule(CC_CALLBACK_1(ListenerSnatch::update, this), _shedulerKey);
			_isSheduling = true;
		}
		return true;
	};

	oneByone->onTouchMoved = [&](Touch*t, Event* e)
	{
		const auto& location = t->getLocation();
		if (!_isDragThreshold)
		{
#ifdef ccc_mouse
#else 
			const auto& top = getChildSnatch(location);
			compareTops(top, _curr, true);
			_curr = top;
#endif
			if (!_curr || !_curr->isEnabled() || _curr->_scrollSpeed == 0 || _curr->_dragType == NodeSnatch::eDrag::None)
			{
				return;
			}
			if (location.distance(t->getStartLocation()) > s_dragThreshold)
			{
				_isDragThreshold = true;
				_vecDrag = _curr->getDragableNode()->getPosition();
				_vecStartLocation = location;
			}
			return;
		}
		if (!_curr || !_curr->isEnabled() || _curr->_dragType == NodeSnatch::eDrag::None)
		{
			_isDragThreshold = false;
			return;
		}
		Node* nodeDrag = _curr->getDragableNode();
		Vec2 v = nodeDrag->getParent()->convertToNodeSpace(location);
		v -= nodeDrag->getParent()->convertToNodeSpace(_vecStartLocation);
		v += _vecDrag;
		switch (_curr->_dragType)
		{
		case NodeSnatch::eDrag::V:
			nodeDrag->setPositionY(v.y);
			_curr->doDraging(location);
			break;
		case NodeSnatch::eDrag::H:
			nodeDrag->setPositionX(v.x);
			_curr->doDraging(location);
			break;
		default:
			nodeDrag->setPosition(v);
			_curr->doDraging(location);
			break;
		}
	};


	oneByone->onTouchCancelled = oneByone->onTouchEnded = [&](Touch*t, Event* e)
	{
		if (_isSheduling)
		{
			EventListenerTouchOneByOne::getAssociatedNode()->unschedule(_shedulerKey);
			_isSheduling = false;
		}
		if (!_curr || !_curr->isEnabled())
		{
			_isDragThreshold = false;
			return;
		}
		_curr->setState(NodeSnatch::eState::Normal);

		const auto& location = t->getLocation();
		if (!_isDragThreshold)
		{
			recurClick(_children, location, _curr);
			if (_curr->isEnabled())
			{
				Node* parent = _curr->_nodeTarget->getParent();
				_curr->doClick(location, true);
				if (parent->getChildren().find(_curr->_nodeTarget) == parent->getChildren().end())
				{
					_curr = nullptr;
				}
			}
		}
		else
		{
			_isDragThreshold = false;
		}
	};
	_nodeTarget->getEventDispatcher()->addEventListenerWithSceneGraphPriority(oneByone, _nodeTarget);

	return true;
}


void ListenerSnatch::addChildSnatch(NodeSnatch *nodeSnatch, bool isAddchildForNodeTarget)
{
	_children.push_back(nodeSnatch);
	if (isAddchildForNodeTarget)
	{
		_nodeTarget->addChild(nodeSnatch->_nodeTarget);
		if (nodeSnatch->_isSortable)
		{
			nodeSnatch->_nodeTarget->setLocalZOrder(++_z);
			sortChildren();
		}
	}
}



// void ListenerSnatch::removeChildSnatch(NodeSnatch *nodeSnatch)
// {
// 	forr(_tops, i)
// 	{
// 		if (_tops[i] == nodeSnatch)
// 		{
// 			_tops.erase(_tops.begin() + i);
// 			return;
// 		}
// 	}
// }


void ListenerSnatch::sortChildren()
{
	std::sort(_children.begin(), _children.end(), [](NodeSnatch* n1, NodeSnatch* n2)
	{
		return n1->_nodeTarget->getLocalZOrder() < n2->_nodeTarget->getLocalZOrder();
	});
}


NodeSnatch* ListenerSnatch::getChildSnatch(const vector<NodeSnatch*>& children, const Vec2& worldSpace)
{
	forr(children, i)
	{
		if (!children[i]->_nodeTarget->isVisible())
		{
			continue;
		}
		const auto& nodeSnatch = getChildSnatch(children[i]->_children, worldSpace);
		if (nodeSnatch)
		{
			return nodeSnatch;
		}
		if (children[i]->isBox(worldSpace))
		{
			return children[i];
		}
	}
	return nullptr;
}


void ListenerSnatch::recurClick(vector<NodeSnatch*>& children, const Vec2& worldSpace, const NodeSnatch* curr)
{
	forr(children, i)
	{
		if (!children[i]->_nodeTarget->isVisible())
		{
			continue;
		}
		if (!children[i]->isEnabled())
		{
			continue;
		}
		recurClick(children[i]->_children, worldSpace, curr);
		if (children[i] == curr)
		{
			continue;
		}

		Node *parent = children[i]->_nodeTarget->getParent();
		children[i]->doClick(worldSpace, false);
		if (parent->getChildren().find(children[i]->_nodeTarget) == parent->getChildren().end())
		{
			children.erase(children.begin() + i);
		}
	}
}



NodeSnatch* ListenerSnatch::getChildSnatch(const Vec2& worldSpace)
{
	return getChildSnatch(_children, worldSpace);
}




bool ListenerSnatch::compareTops(NodeSnatch* top, NodeSnatch* old, bool isTouching)
{
	if (top == old)
	{
		if (isTouching)
		{
			if (top && top->isEnabled())
			{
				top->setState(NodeSnatch::eState::Down);
			}
			return true;
		}
		return false;
	}
	if (old)
	{
		if (old->isEnabled())
		{
			old->setState(NodeSnatch::eState::Normal);
		}
	}
	if (top)
	{
		if (top->isEnabled())
		{
			top->setState(isTouching ? NodeSnatch::eState::Down : NodeSnatch::eState::Cross);
		}
	}
	return true;
}



void ListenerSnatch::update(float delta)
{
	_delta += delta;
	if (_curr && _curr->isEnabled())
	{
		Node *parent = _curr->_nodeTarget->getParent();
		_curr->doKeep(_delta);
		if (parent->getChildren().find(_curr->_nodeTarget) == parent->getChildren().end())
		{
			_curr = nullptr;
		}
	}
}



